package lookas.trip;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import lookas.dbaccess.DBException;
import lookas.dbaccess.DBFactory;
import lookas.foto.FotoPosition;
import lookas.track.GPSPosition;
import lookas.track.Point;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cmc.sanselan.ImageReadException;
import org.cmc.sanselan.ImageWriteException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class Trip{
    private static final Log    log        = LogFactory.getLog(Trip.class);
    private static final String XSD        = "http://nauka.googlecode.com/svn/trunk/JFotoGPS/xsd/tracks.xsd";
    private GPSTracks           trasy      = new GPSTracks();
    private List<FotoPosition>  fotosPos   = new ArrayList<FotoPosition>();
    private int                 delta      = 0;
    private String              parrentDir = null;

    public class GPSTracks extends TreeMap<GPSPosition, TreeSet<GPSPosition>>{
        private static final long serialVersionUID = 8478470141810681611L;

        public void put(TreeSet<GPSPosition> trasa){
            this.put(trasa.first(), trasa);
        }
    }

    public GPSTracks getTrasy(){
        return trasy;
    }

    public void optimalize(double scale) throws Exception{
        double d = Math.pow(10, -10) * scale;
        log.info("Optymalizacja ze skala " + scale);
        for(TreeSet<GPSPosition> trasa: trasy.values()){
            TreeSet<GPSPosition> rem = new TreeSet<GPSPosition>();
            GPSPosition x = null;
            GPSPosition y = null;
            GPSPosition z = null;
            GPSPosition r = null;
            int przed = trasa.size();
            log.info("Trasa z punktami> " + przed);
            for(GPSPosition position: trasa){
                z = position;
                if(x == null){
                    x = position;
                    continue;
                }
                if(y == null){
                    r = y = position;
                    continue;
                }
                double abs = Math.abs(Point.wspolLiniowe(x.getPosition(), y
                    .getPosition(), z.getPosition()));
                if(abs < d){
                    rem.add(r);
                    r = z;
                }else{
                    x = r;
                    y = z;
                    r = y;
                }
            }
            trasa.removeAll(rem);
            int po = trasa.size();
            log.info("Trasa po optymalizacji> " + po);
            log.info(String.format("Usunieto> %.2f%%", (przed - po)
                / (double)przed * 100));
        }
    }

    public void setParrentDir(String parrentDir){
        this.parrentDir = parrentDir;
    }

    public Trip(String dbFile){
        this.readDB(new File(dbFile));
    }

    public GPSPosition getPosByDate(Calendar date){
        TreeSet<GPSPosition> keySet = new TreeSet<GPSPosition>();
        keySet.addAll(trasy.keySet());
        keySet.add(trasy.get(keySet.last()).last());
        GPSPosition key = null;
        for(GPSPosition position: keySet){
            if(date.getTimeInMillis() > position.getDate().getTimeInMillis())
                key = position;
        }
        if(key == null)
            return null;
        TreeSet<GPSPosition> trasa = trasy.get(key);
        if(trasa == null)
            return null;

        long min = Long.MAX_VALUE;
        GPSPosition gpsMin = null;
        for(GPSPosition pos: trasa){
            long t = Math.abs(pos.getDate().getTimeInMillis()
                - date.getTimeInMillis());
            if(t < min){
                min = t;
                gpsMin = pos;
            }
        }
        return gpsMin;
    }

    public void setDelta(int delta){
        this.delta = delta;
        for(FotoPosition pos: fotosPos){
            pos.setDeltaTime(delta);
        }
    }
    
    public void addFoto(File fotoFile){
        FotoPosition foto = null;
        try{
            foto = new FotoPosition(fotoFile);
            foto.setDeltaTime(delta);
        }catch(Exception e){
            throw new RuntimeException(fotoFile.getName(), e);
        }
        GPSPosition point = getPosByDate(foto.getDate());
        if(point != null){
            foto.setPosition(point.getPosition());
            fotosPos.add(foto);
            log.info("Dodano zdjecie " + fotoFile.getName());
        }else{
            log.info("Pominieto zdjecie " + fotoFile.getName());
        }
    }

    public void writeFotoGPS(){
        for(FotoPosition fPos: fotosPos){
            try{
                fPos.writeGPSPosition();
                log.info("Zapisano " + fPos.getFoto().getAbsolutePath());
            }catch(ImageReadException e){
                log.warn("Blad czytania " + fPos.getFoto().getAbsolutePath());
            }catch(IOException e){
                log.warn("Blad pliku " + fPos.getFoto().getAbsolutePath());
            }catch(ImageWriteException e){
                log.warn("Blad pisania " + fPos.getFoto().getAbsolutePath());
            }
        }
    }

    public void addFotos(File fotosDirectory){
        File[] files = fotosDirectory.listFiles(new FileFilter(){
            public boolean accept(File pathname){
                return pathname.getName().toLowerCase().endsWith("jpg")
                    || pathname.getName().toLowerCase().endsWith("jpeg");
            }
        });

        for(File file: files){
            addFoto(file);
        }
    }

    public void addFotos(List<File> fotos){
        for(File fotoFile: fotos){
            addFoto(fotoFile);
        }
    }

    private void readDB(File dbFile){
        log.info("Czytanie bazy");
        try{
            DBFactory.open(dbFile);
            PreparedStatement st = DBFactory
                .prepareStatement("SELECT data FROM TRACKS_V1");
            ResultSet rs = st.executeQuery();
            int x = 0;
            while(rs.next()){
                x++;
                TreeSet<GPSPosition> trasa = new TreeSet<GPSPosition>();
                byte[] obj = (byte[])rs.getObject(1);
                for(int i = 0; i < obj.length; i += 12){
                    long arr2long = arr2long(obj, i);
                    long arr2long2 = arr2long(obj, i + 8);
                    long arr2long3 = arr2long(obj, i + 4);
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(arr2long * 1000);
                    GPSPosition position = new GPSPosition(c, new Point(
                        (double)arr2long2 / 0x800000,
                        (double)arr2long3 / 0x800000));
                    trasa.add(position);
                }
                trasy.put(trasa);
            }
        }catch(DBException e){
            throw new RuntimeException(e);
        }catch(SQLException e){
            throw new RuntimeException(e);
        }finally{
            try{
                DBFactory.close();
            }catch(DBException e){
                throw new RuntimeException(e);
            }
        }
    }

    public Document getXMLDocument() throws ParserConfigurationException{

        DocumentBuilder db = DocumentBuilderFactory.newInstance()
            .newDocumentBuilder();
        Document xmldoc = db.newDocument();
        Element trip = xmldoc.createElement("trip");
        trip.setAttribute("xmlns:xsi",
            "http://www.w3.org/2001/XMLSchema-instance");
        trip.setAttribute("xsi:noNamespaceSchemaLocation", XSD);
        xmldoc.appendChild(trip);

        // fotos
        Element fotos = xmldoc.createElement("fotos");
        trip.appendChild(fotos);
        for(FotoPosition fpos: fotosPos){
            Element foto = xmldoc.createElement("foto");
            fotos.appendChild(foto);
            foto.setAttribute("date", String.format("%1$tFT%1$tT", fpos
                .getDate()));
            if(parrentDir == null)
                parrentDir = fpos.getFoto().getParent() + File.separator;
            foto.setAttribute("src", parrentDir + fpos.getFoto().getName());
            Element lat = xmldoc.createElement("lat");
            foto.appendChild(lat);
            lat.setTextContent(String.valueOf(fpos.getPosition().getLat()));
            Element lon = xmldoc.createElement("lon");
            foto.appendChild(lon);
            lon.setTextContent(String.valueOf(fpos.getPosition().getLon()));
        }

        // tracks
        Element tracks = xmldoc.createElement("tracks");
        trip.appendChild(tracks);
        for(GPSPosition key: trasy.keySet()){
            Element track = xmldoc.createElement("track");
            tracks.appendChild(track);
            track.setAttribute("start", String.format("%1$tFT%1$tT", key
                .getDate()));
            for(GPSPosition pos: trasy.get(key)){
                Element point = xmldoc.createElement("point");
                track.appendChild(point);
                point.setAttribute("date", String.format("%1$tFT%1$tT", pos
                    .getDate()));
                Element lat = xmldoc.createElement("lat");
                point.appendChild(lat);
                lat.setTextContent(String.valueOf(pos.getPosition().getLat()));
                Element lon = xmldoc.createElement("lon");
                point.appendChild(lon);
                lon.setTextContent(String.valueOf(pos.getPosition().getLon()));
            }
        }
        return xmldoc;
    }

    private long arr2long(byte[] b, int start){
        long l = 0;
        l |= b[start + 3] & 0xFF;
        l <<= 8;
        l |= b[start + 2] & 0xFF;
        l <<= 8;
        l |= b[start + 1] & 0xFF;
        l <<= 8;
        l |= b[start] & 0xFF;
        return l;
    }
}
